﻿/* ----------------------------------------------------------------------------
 * Copyright (c) Philip Stears 
 *
 * This source code is subject to terms and conditions of the Microsoft Public 
 * License (Ms-PL).
 * 
 * By using this source code in any fashion, you are agreeing to be bound 
 * by the terms of the Microsoft Public License.
 * 
 * To request a copy of the Microsoft Public License, send a request by email
 * to license@connectbasic.net.
 *
 * This notice must not be removed from this software.
 * ---------------------------------------------------------------------------- */

namespace ConnectBasic.ObjectModel
{
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using ConnectBasic.Execution;
    using Microsoft.Scripting;
    using DlrExpression = Microsoft.Linq.Expressions.Expression;

    /// <summary>
    /// Represents an IF statement.
    /// </summary>
    public sealed class IfStatement : StatementNodeBase
    {
        private ReadOnlyCollection<ConditionalBlock> mConditionalBlocks;
        private Block mElseBlock;
        private SourceSpan mOpeningSpan;

        /// <summary>
        /// Initializes a new instance of the <see cref="IfStatement" /> class.
        /// </summary>
        /// <param name="openingSpan">The span from the "If" to the "Then" keyword, including the keywords themselves.</param>
        /// <param name="conditionalBlocks">The If and ElseIf blocks, there must be at least one.</param>
        /// <param name="elseBlock">The (optional) else block.</param>
        public IfStatement(SourceSpan openingSpan, IEnumerable<ConditionalBlock> conditionalBlocks, Block elseBlock)
        {
            mOpeningSpan = openingSpan;
            mConditionalBlocks = new ReadOnlyCollection<ConditionalBlock>(conditionalBlocks as IList<ConditionalBlock> ?? new List<ConditionalBlock>(conditionalBlocks));
            mElseBlock = elseBlock;
        }

        /// <summary>
        /// Gets the span from the "If" to the "Then" keyword, including the keywords themselves.
        /// </summary>
        public SourceSpan OpeningSpan
        {
            get { return mOpeningSpan; }
        }

        /// <summary>
        /// Gets the conditional blocks which make up the if-statement.
        /// </summary>
        public IEnumerable<ConditionalBlock> ConditionalBlocks
        {
            get { return mConditionalBlocks; }
        }

        /// <summary>
        /// Gets the optional else block.
        /// </summary>
        public Block ElseBlock
        {
            get { return mElseBlock; }
        }

        /// <inheritdoc />
        public override DlrExpression Compile(BasicCodeGenerator gen)
        {
            var conditionals = mConditionalBlocks;

            // Generate the last test in the if statement
            var lastConditional = conditionals[conditionals.Count - 1];

            var current = (DlrExpression)DlrExpression.Condition
                (
                GeneratorUtil.Convert(lastConditional.Condition.Compile(gen), typeof(bool)),
                DlrExpression.Block(typeof(void), lastConditional.Compile(gen)),
                mElseBlock != null ? (DlrExpression)DlrExpression.Block(typeof(void), mElseBlock.Compile(gen)) : (DlrExpression)DlrExpression.Empty()
                );

            // And work backwards to build the complete if statement (if for-example they have
            // some ElseIf blocks
            for (int i = conditionals.Count - 2; i >= 0; i--)
            {
                var thisConditional = conditionals[i];

                current = DlrExpression.Condition
                    (
                    GeneratorUtil.Convert(thisConditional.Condition.Compile(gen), typeof(bool)),
                    DlrExpression.Block(typeof(void), thisConditional.Compile(gen)),
                    current
                    );
            }

            // If we've got debugging information, add it now
            current = gen.AddDebugInfo(current, mOpeningSpan);

            return current;
        }
    }
}
